/**********************************************************************************************************************
 * @file    misc.h
 * @author  Queclink Hubert.Lee
 * @date    2018-07-04
 * @brief   Miscellaneous, 混杂的, 各种各样的定义以及函数.
 * @ingroup common
 * Copyright (C) 2018 Queclink Wireless Solutions (ShenZhen) Co., Ltd. All Rights Reserved.
 *
 * @attention
 *
 * @usage
 *
 *********************************************************************************************************************/


#ifndef __QLSDK_MISC_H
#define __QLSDK_MISC_H

/* includes ---------------------------------------------------------------------------------------------------------*/
#include <time.h>
#include <FreeRTOS.h>

#include <global.h>

#ifdef __cplusplus
extern "C" {
#endif

#define is_line_feed_char(ch)                   (('\r' == (ch)) || ('\n' == (ch)))

/*
 * @brief  支持空字段的字符串分割函数
 * @note   1.函数的使用方法与 strtok 完全一致, 但区别在于, 当'str'包含连续的分隔符时,
 *          重复的各个分隔符会被识别为"空字段", 返回指向空字符串的指针, 如 "AT+XXX=1,2,,,3$"
 *          若以"=,"为分隔符, 本函数会将其分割成 6 个字段: "AT+XXX" "1" "2" "" "" "3$"
 *         2.本函数不是线程安全的函数, 使用时需注意安全.
 * @param  str: [IO], 待分割的字符串, 要求可写;
 *         delim: [I], 字段分隔符, 字符串格式.
 * @retval NULL:没有更多的字段,分割结束, others:分割到的字段的起始指针.
 */
char *qlsdk_strtok(char *str, const char *delim);

/* qlsdk_strtok 的线程安全版本 */
char *qlsdk_strtok_r(char *str, const char *delim, char **saveptr);

/*
 * @brief  计算以 qlsdk_strtok 的方式分割字符串时分割到的参数总个数
 * @note   函数返回值(参数总个数)必须与 qlsdk_strtok 分割到的一致.
 * @param  str: [I], 待分割的字符串;
 *         delim: [I], 字段分隔符, 字符串格式.
 * @retval 非负数:参数总个数, 负数:错误码.
 */
int qlsdk_strtok_count(const char *str, const char *delim);

/*
 * @brief  数组前cnt个数字转整形, 功能类似atoi
 * @param  str: [I], 待转换数组;
 *         cnt: [I], 只转换数组的前cnt个数字;
 * @retval 转换结果
 */
int qlsdk_atoi(const char *str, size_t cnt);

/*
 * @brief  数组前cnt个数字转长整形, 功能类似strtol
 * @param  str: [I], 待转换数组;
 *         cnt: [I], 只转换数组的前cnt个数字;
 *         base: [I], 进制, 如10,16.
 * @retval 转换结果
 */
long int qlsdk_strtol(const char *str, size_t cnt, int base);

/*
 * @brief  替换字符串中的字符(把字符串str中的victim全替换为c)
 * @param  str: [IO], 待处理的字符串;
 *         victim: [I], 需要被替换的字符, 字符串格式;
 *         c: [I], 替换成该字符.
 * @note   str="a b,c", victim=" ,", c='_', 执行函数后str="a_b_c".
 * @retval 0:成功, others:错误码.
 */
int qlsdk_strrepl(char *str, const char *victim, const char c);

/*
 * @brief  修剪字符串(去掉字符串str前后的trim字符)
 * @param  str: [IO], 待修剪的字符串;
 *         trim: [I], 前后需要去掉的字符, 字符串格式.
 * @note   str=", a b,c,", c=" ,", 执行函数后str="a b,c".
 * @retval 0:成功, others:错误码.
 */
int qlsdk_strtrim(char *str, const char *trim);

/*
 * @brief  检查字符串的最后一个字符是否指定字符
 * @param  str: [I], 待检查的字符串;
 *         tail: [I], 最后一个字符指定的字符.
 * @retval false:最后字符不是tail,true:最后字符是tail.
 */
bool qlsdk_strtail(const char *str, char tail);

/*
 * @brief  在字符串末尾追加"\r\n"
 * @note   1.若原字符串末尾已有相应字符, 不重复追加;
 *         2.请用户确保追加"\r\n"不会溢出缓存区.
 * @param  str: [IO], 待追加"\r\n"的字符串.
 * @retval none.
 */
void qlsdk_strlf_add(char *str);

/*
 * @brief  从字符串的末尾开始倒序查找字符
 * @param  str: [I], 待查找的字符串;
 *         delim: [I], 待查找的字符, 字符串格式, 查找到任一字符终止.
 * @note   从 str 末尾开始倒序查找 delim 字符串中任意一个字符.
 * @retval NULL:无对应字符, others:查找到的字符的指针.
 */
char *qlsdk_strchr_last(const char *str, const char *delim);

/*
 * @brief  修剪内存数据(去掉前面的空白字符和控制字符)
 * @param  buf: [IO], 待修剪的内存数据;
 *         n: [I], 待修剪的内存数据的长度.
 * @retval 非负数:修剪后的数据长度, 负数:错误码.
 */
ssize_t qlsdk_memtrim(char *buf, size_t n);

/*
 * @brief  从缓存区中查找字符串(不包括'\0'), 查找到第1个字符串返回.
 * @param  buf: [I], 缓存区;
 *         n: [I], 缓存区长度;
 *         str: [I], 待查找字符串;
 * @retval NULL:未找到, others:找到字符串str,返回其在缓存区中的首地址.
 */
char *qlsdk_memstr(const void *buf, size_t n, const char *str);

/*
 * @brief  在内存中不区分大小写查找字符串
 * @note   1.在内存中查找不同于在字符串中查找;
 *         2.本函数与 strstr 类似, 仅查找首个匹配的字符串.
 * @param  buf: [I], 待查找的内存数据;
 *         n: [I], 待查找的内存数据长度;
 *         str: [I], 待查找的字符串.
 * @retval NULL:内存中没有要找的字符串, others:找到的字符串的指针.
 */
char *qlsdk_memcasestr(const void *buf, size_t n, const char *str);

/*
 * @brief  从缓存区中查找字符串(不包括'\0'), 查找到最后1个字符串返回.
 * @param  buf: [I], 缓存区;
 *         n: [I], 缓存区长度;
 *         str: [I], 待查找字符串;
 * @retval NULL:未找到, others:找到字符串str,返回其在缓存区中的首地址.
 */
char *qlsdk_memstr_last(const void *buf, size_t n, const char *str);

/*
 * @brief  在内存中查找出现目标字符串中任一字符的位置
 * @param  buf: [I], 待查找的内存数据;
 *         n: [I], 待查找的内存数据长度;
 *         str: [I], 待查找的字符串, 查找到其中任一字符退出.
 * @retval NULL:内存中没有要找的字符, others:找到的字符的指针.
 */
char *qlsdk_memsrch_each(const void *buf, size_t n, const char *str);

/*
 * @brief  判断内存buf1是否包含buf2.
 * @note   1.本函数与 strstr 类似, 仅查找首个匹配的内存地址.
 * @param  buf1: [I], 被查找的目标内存"父串";
 *         n1: [I], 待查找的"父串"数据长度;
 *         buf2: [I], 要查找的内存对象"子串";
 *         n1: [I], 待查找的"子串"数据长度;
 * @retval NULL:内存中没有要找的"子串", others:找到的"子串"的指针.
 */
void *qlsdk_memsrch(const void *buf1, size_t n1, const void *buf2, size_t n2);

/*
 * @brief  计算缓存区中包含的某字符的个数(buf中有几个c)
 * @param  buf: [I], 缓存区;
 *         cnt: [I], 缓存区长度;
 *         c: [I], 待统计的字符.
 * @retval 字符个数.
 */
unsigned int qlsdk_char_amount(const char *buf, size_t cnt, const char c);

/*
 * @brief  计算16位CRC校验值(CRC-16,0x8005,Modbus)
 * @param  buf: [I],待计算数据;
 *         cnt: [I],待计算字节数.
 * @retval 16位CRC校验值
 */
unsigned short qlsdk_crc16_modbus(const unsigned char *buf, unsigned int cnt);

/*
 * @brief  计算16位CRC校验值(CRC-CCITT(0xFFFF),0x1021)
 * @param  buf: [I],待计算数据;
 *         cnt: [I],待计算字节数.
 * @retval 16位CRC校验值
 */
unsigned short qlsdk_crc16_ccitt(const unsigned char *buf, unsigned int cnt);

/*
 * @brief  判断多个字符是否全为数字
 * @param  buf: [I],待判断字符;
 *         cnt: [I],待判断字符数.
 * @retval true:全为数字, false:不全为数字.
 */
bool qlsdk_is_all_numeric(const char *buf, size_t cnt);

/*
 * @brief  把字符转换为字节, 如 'A''8'=>'A''8'0xA8, '7''C'=>0x7C
 * @param  chars: [I], 待转换字符;
 *         cnt: [I], 需转换的字符长度;
 *         bytes: [O], 转换到的字节;
 * @retval 0:成功, others:错误码
 */
int qlsdk_chars_to_bytes(char *chars, size_t len, unsigned char *bytes);

/*
 * @brief  把字节转换为2字符, 如 0xA8=>'A''8', 0x7C=>'7''C'
 * @param  byte: [I], 待转换字节;
 *         char1: [O], 转换到的第一个字符, 对应 byte 的高 4 位;
 *         char2: [O], 转换到的第二个字符, 对应 byte 的低 4 位;
 * @retval 0:成功, others:错误码
 */
int qlsdk_byte_to_chars(unsigned char byte, char *char1, char *char2);

/*
 * @brief  把字节转换为字符, 如 0xA8,0x0D,0x3E=>'A''8''0''D''3''E'
 * @param  bytes: [I], 待转换字节;
 *         count: [I], 待转换字节个数;
 *         chars: [O], 转换到的字符, 个数固定为 2*count;
 * @retval 0:成功, others:错误码
 */
int qlsdk_bytes_to_chars(unsigned char *bytes, size_t count, char *chars);

/*
 * @brief  计算时间差(new-last)
 * @param  new: [I], 新的时间;
 *         last: [I], 之前的时间.
 * @retval 时间差, 单位s, 正数表示new比last晚, 负数表示new比last早.
 */
int qlsdk_difftime(time_t new, time_t last);

/*
 * @brief  重新计算星期几
 * @param  tm: [IO], 日期时间, 根据其中的 tm_year,tm_mon,tm_mday, 重新计算 tm_wday.
 * @retval none.
 */
void qlsdk_tm_wday_update(struct tm *tm);

/*
 * @brief  获取文件名函数
 * @param  path: [I], 完整路径, 字符串;
 *         basename: [O], 文件名, 字符串.
 * @note   函数功能与basename一致.
 * @retval 0:成功, others:错误码.
 */
int qlsdk_basename(const char *path, char *basename);

/*
 * @brief  获取目录名函数
 * @param  path: [I], 完整路径, 字符串;
 *         dirname: [O], 目录名, 字符串.
 * @note   函数功能与dirname一致.
 * @retval 0:成功, others:错误码.
 */
int qlsdk_dirname(const char *path, char *dirname);

/*
 * @brief  获取两位置点之间的距离
 * @note
 * @param  lat1: [I], 位置1纬度信息,度;
 *		   lng1: [I], 位置1经度信息,度;
 *		   lat2: [I], 位置2纬度信息,度;
 *		   lng2: [I], 位置2经度信息,度;
 * @retval 两位置点之间的距离,km
 */
double qlsdk_gnss_calculate_distance(double lat1, double lng1, double lat2, double lng2);

/*   @brief  从字符串(一般为AT指令)中以第一个':'为起始字符, 以','为分隔符查找对应的字段
 *   @note   自动跳过找到的字段前面的空格;'\r''\n'也作为分隔符使用.
 *   @param  str: [I], 待查找字符串;
 *           index: [I], 待查找字段号, 从0开始;
 *           size: [O], 查找到的字段长度, 仅当函数返回非空时有效; */
const char *qlsdk_at_field_search(const char *str, int index, size_t *size);

#ifdef __cplusplus
}
#endif

#endif /* __QLSDK_MISC_H */
